#include <dos.h>
#include "prochan.h"
#include "mpx_supt.h"
#include "IOHAN.H"

dcb comPort;
void interrupt (*oldfunc) (void);
void interrupt(*new_handler)(void);
extern pcb *cop;
extern iocb com;
extern iocb term;

void addIOD(iod* newRequest, iocb *queue)
{
	if(queue->count == 0)
	{
		queue->head = newRequest;
		queue->tail = newRequest;
		queue->count++;
	}
	else
	{
		queue->tail->next = newRequest;
		queue->count++;
	}
}

iod* removeIOD(iocb *queue)
{
	iod *tempRequest = NULL;
	
	if(queue->count == 0)
	{
		output("The I/O queue is already empty!", 1);
	}
	else
	{
		tempRequest = queue->head;
		if(tempRequest->next == NULL)
		{
			queue->head = NULL;
			queue->tail = NULL;
		}
		else
		{
			queue->head = queue->head->next;
		}
		queue->count--;
	}
    return tempRequest;
}

/** \brief Closes the communication port.
*/
int com_close (void){
	int mask;
	if (comPort.open == 0){
		return ERR_SERIAL_PORT_NOT_OPEN;
	}
	else {
		comPort.open = 0;
		disable();
		mask = inportb(PIC_MASK);
		mask = mask & ~0x80;
		outportb(PIC_MASK, mask);
		outportb(COM1_MS, 0x00);
		outportb(COM1_INT_EN, 0x00);
		enable();
		setvect(COM1_INT_ID , oldfunc);
	}
	return 0;
}

/** \brief Opens the communication port.
*/
int com_open( int *eflag_p, int baud_rate)
{
	int baud_rate_div;
	int mask;
	if(eflag_p==NULL)
    return INVALID_EVEN_FLAG_POINTER;
	else if( baud_rate <= 0)
    return INVALID_BAND_RATE_DIVISOR;
	else if(comPort.open==1)
    return ERROR_PORT_ALREADY_OPEN;
	else
	{
		comPort.open=1;
		comPort.eventFlag=eflag_p;
		comPort.status=IDLE;
		comPort.ring_buffer_in=0;
		comPort.ring_buffer_out=0;
		comPort.ring_buffer_count=0;
		oldfunc=getvect(COM1_INT_ID);
		setvect(COM1_INT_ID, &comHandler);
		baud_rate_div=115200/(long) baud_rate;
		outport(COM1_LC, 0x80);
		outportb( COM1_BRD_LSB, baud_rate_div&0xFF);
		outportb(COM1_BRD_MSB, (baud_rate_div >> 8) & 0xFF);
		outport(COM1_LC,0x03);
		disable();
		mask = inportb(PIC_MASK);
		mask = mask ||0x80;
		outportb(PIC_MASK, mask);
		enable();
		outport(COM1_MC,0x08);
		outport(COM1_INT_EN,0x01);
	}
	return 0;
}//end_com_open

/** \brief Writes to the communication port.
	* Writes the first character to the device and then sets the device status to WRITE
	* @param buf_p char - Character buffer to be written.
	* @param count_p int - the number of characters to be written from the buffer
	* @return int - Zero if no problems encountered, an error code otherwise.
*/
int com_write(char *buf_p, int *count_p)
{
	iod *temp;
	int mask;
	if(comPort.open==0)
    return SERIAL_PORT_NOT_OPEN;
	else if(comPort.status!=IDLE)
	return DEVICE_BUSY;
	if(buf_p==NULL)
    return INVALID_BUFFER_ADDRESS;
	else if( count_p==NULL || *count_p<=0)
    return INVALID_COUNT_ADDRESS_OR_COUNT_VALUE;
	else
	{
		comPort.out_buff=buf_p;
		comPort.out_done=0;
		comPort.out_count=count_p;
		comPort.status=WRITE;
		*comPort.eventFlag=0;
		outportb(COM1_BASE, comPort.out_buff[0]);
		comPort.out_buff++;
		comPort.out_done++;
		disable();
		mask = inportb(COM1_INT_EN);
		mask = mask | 0x02;
		outportb(COM1_INT_EN,mask);
		enable();
		return 0;
	}
}


/** \brief Reads from the communication port.
	* Sets the device status to READING. Coppies from the ring buffer to buf_p until it is empty or
	* encounters a return character ("\r"). If the read process is not complete then it returns Zero.
	* If it is then it returns the number of characters read.
	* @param buf_p char - buffer to be written to.
	* @param count_p int - the number of characters to be read.
	* @return int - Zero if no operation complete, an error code or the number of characters read otherwise.
*/
int com_read (char *buf_p, int *count_p){
	iod *temp;
	if (comPort.status != IDLE){
		return DEVICE_BUSY;
	}
	if (comPort.open != 1){
		return SERIAL_PORT_NOT_OPEN;
	}
	if (comPort.ring_buffer == NULL){
		return INVALID_BUFFER_ADDRESS;
	}
	comPort.in_buff = buf_p;
	comPort.in_count = count_p;
	comPort.in_done = 0;
	*comPort.eventFlag = 0;
	disable();
	comPort.status = READING;
	while (comPort.ring_buffer_count != 0  && comPort.in_done != *comPort.in_count){
		if (comPort.ring_buffer[comPort.ring_buffer_out] == '\r'){
			*(comPort.in_buff-1) = '\0';
			break;
		}
		comPort.in_buff[0] = comPort.ring_buffer[comPort.ring_buffer_out];
		comPort.in_buff++;
		comPort.ring_buffer_count--;
		comPort.ring_buffer_out++;
		if (comPort.ring_buffer_out == 200){
			comPort.ring_buffer_out = 0;
		}
	}
	enable();
	if (comPort.in_done != *comPort.in_count){
		return 0;
	}
	*(comPort.in_buff-1) = '\0';
	comPort.status = IDLE;
	*comPort.eventFlag = 1;
	return comPort.in_done;
}

/** \brief Communication port handler.
	* Called when an input or output action is completed.
	* Determines which action has occured and calls the proper secondary interrupt.
*/
void interrupt comHandler(){
	int mask;
	if(comPort.open==0){
		outportb(INTERRUPT_FLAG, 0x20);
	}
	else {
		mask = inportb(COM1_INT_ID_REG);
		mask = mask & 0x07;
		if (mask == 2){
			comWrite();
		}
		else if (mask == 4){
			comRead();
		}
		outportb(INTERRUPT_FLAG, 0x20);
	}
}


/** \brief Write secondary interrupt.
	* Writes the next character from the buffer to the device.
	* If all characters have been written sets the device status to IDLE, sets the event flag, and disables
	* writing.
*/
void comWrite(){
	int mask;
	if (comPort.status != WRITE){
		return;
	}
	if (comPort.out_done != *comPort.out_count){
		outportb(COM1_BASE, comPort.out_buff[0]);
		comPort.out_buff++;
		comPort.out_done++;
	}
	else {
		comPort.status = IDLE;
		*comPort.eventFlag = 1;
		mask = inportb(COM1_INT_EN);
		mask = mask & ~0x02;
		outportb(COM1_INT_EN ,mask);
	}
	
}

/** \brief Read secondary interrupt.
	* If the device status is not READING the character is read and placed in the ring buffer.
	* If status is READING the character is placed in the current read buffer. If the read
	* buffer is full or a return character ('\r') is encountered the device status is set to
	* IDLE, the event flag is set, and the count is set to the count done.
*/
void comRead(){
	char input;
	input = inportb(COM1_BASE);
	if (comPort.status != READING){
		if (comPort.ring_buffer_count != 200){
			comPort.ring_buffer[comPort.ring_buffer_in] = input;
			comPort.ring_buffer_count++;
			comPort.ring_buffer_in++;
			if (comPort.ring_buffer_in == 200){
				comPort.ring_buffer_in = 0;
			}
		}
		return;
	}
	comPort.in_buff[0] = input;
	comPort.in_buff++;
	comPort.in_done++;
	if (comPort.in_done == *comPort.in_count | input == '\r'){
		*(comPort.in_buff-1) = '\0';
		comPort.status = IDLE;
		*comPort.eventFlag = 1;
		*comPort.in_count = comPort.in_done;
	}
}

void initIO(int *terminal, int *comPort){
	com_open(comPort, 1200);
	trm_open(terminal);
}